package butines.core;

import java.awt.DisplayMode;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import static java.awt.RenderingHints.*;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.swing.JFrame;

public class ScreenManager {

	private static GraphicsDevice device;
	static {
		NullRepaintManager.install();
		GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
		device = environment.getDefaultScreenDevice();
	}

	private JFrame      jFrame;
	private Graphics2D  graphics;
	private DisplayMode desktopDisplayMode;
	
	private String 		displayModeAsStr;
	private DisplayMode displayMode;
	private boolean     fullScreen;
	
	private DisplayMode newDisplayMode;
	private boolean     newFullScreen;
	
	private Map<String, CompatiblesDisplay> compatiblesDisplayMap  = new HashMap<String, CompatiblesDisplay>();
	private List<CompatiblesDisplay>        compatiblesDisplayList = new LinkedList<CompatiblesDisplay>();
	
	public ScreenManager() {
		jFrame = new JFrame(device.getDefaultConfiguration());
		jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jFrame.setUndecorated(true);
		jFrame.setIgnoreRepaint(true);
		jFrame.setResizable(false);
		jFrame.setFocusTraversalKeysEnabled(false);
		
		desktopDisplayMode = device.getDisplayMode();
		
		findCompatiblesDisplayMode();
		
		int bitDepth = desktopDisplayMode.getBitDepth();
		
		newDisplayMode = compatiblesDisplayList.get(0).getDisplayModeByBitDepth(bitDepth);
		newFullScreen = false;
	}
	
	public DisplayMode getDesktopDisplayMode() {
		return desktopDisplayMode;
	}
	
	public String getDisplayModeAsStr() {
		return displayModeAsStr;
	}
	
	public Graphics2D getGraphics() {
		if (jFrame == null) {
			return null;
		}
		if (graphics == null) {
			BufferStrategy strategy = jFrame.getBufferStrategy();
			graphics = (Graphics2D) strategy.getDrawGraphics();
			graphics.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
			graphics.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR);
			graphics.setRenderingHint(KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_QUALITY);
			graphics.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
			graphics.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_OFF);
		}
		return graphics;
	}

	public void update() {
		if (graphics != null) {
			graphics.dispose();
			graphics = null;
		}
		BufferStrategy strategy = jFrame.getBufferStrategy();
		if (strategy.contentsLost()) {
			return;
		}
		strategy.show();
		// Sync the display on some systems.
		// (on Linux, this fixes event queue problems)
		Toolkit.getDefaultToolkit().sync();
		
	}

	private String formatDisplayMode(DisplayMode dm) {
		return dm.getWidth() + "x" + dm.getHeight() + "x" + dm.getBitDepth() + "@" + dm.getRefreshRate();
	}
	
	private void applyFullScreenMode() {
		if (device.isFullScreenSupported()) {	
			if (newFullScreen) {
				jFrame.setVisible(false);
				device.setFullScreenWindow(jFrame);
				fullScreen = true;
			} else {
				jFrame.setVisible(true);
				device.setFullScreenWindow(null);
				fullScreen = false;
			}
		}
	}
	
	private void applyDisplayMode() {
		if (device.isDisplayChangeSupported()) {
			try {
				device.setDisplayMode(newDisplayMode);
			} catch (IllegalArgumentException ex) {
				// ignored
				ex.printStackTrace();
				fullScreen = false;
			}
		}
		jFrame.setSize(this.newDisplayMode.getWidth(), newDisplayMode.getHeight());
		displayMode = newDisplayMode;
		displayModeAsStr = formatDisplayMode(newDisplayMode);
	}
	
	private void createBuffserStrategy() {
		// avoid potential deadlock in 1.4.1_02
		try {
			EventQueue.invokeAndWait(new Runnable() {
				public void run() {
					jFrame.createBufferStrategy(2);
				}
			});
		} catch (InterruptedException ex) {
			// ignore
		} catch (InvocationTargetException ex) {
			// ignore
		}
	}

	public void setDisplayMode(int width, int height, int bitDepth) {
		setDisplayMode(width, height, bitDepth, desktopDisplayMode.getRefreshRate());
	}

	public void setDisplayMode(String resolution) {
		String[] values = resolution.split("@");
		int refreshRate = Integer.valueOf(values[1]);
		
		values = values[0].split("x");
		int width = Integer.valueOf(values[0]);
		int height = Integer.valueOf(values[1]);
		int bitDepth = Integer.valueOf(values[2]);
		
		setDisplayMode(width, height, bitDepth, refreshRate);
	}
	
	public void setDisplayMode(int width, int height, int bitDepth, int refreshRate) {
		newDisplayMode = getCompatibleDisplayMode(width, height, bitDepth, refreshRate);
		if (newDisplayMode == null) {
			newDisplayMode = new DisplayMode(width, height, bitDepth, refreshRate);
		}
	}
	
	public void setFullScreen(boolean fullScreen) {
		this.newFullScreen = fullScreen;
	}
	
	public boolean isFullScreen() {
		return fullScreen;
	}
	
	public DisplayMode getDisplayMode() {
		return displayMode;
	}

	public void refresh() {
		applyFullScreenMode();
		applyDisplayMode();
		createBuffserStrategy();
		centerWindow();
		jFrame.requestFocus();
	}
	
	public void dispose() {
		device.setFullScreenWindow(null);
		jFrame.dispose();
	}

	public JFrame getWindow() {
		return jFrame;
	}

	public void centerWindow() {
		if (!isFullScreen()) {
			int left = (desktopDisplayMode.getWidth() - jFrame.getWidth()) / 2;
			int top = (desktopDisplayMode.getHeight() - jFrame.getHeight()) / 2;
			jFrame.setLocation(left, top);
		}
	}

	public static BufferedImage createCompatibleImage(int w, int h, int transparency) {
		GraphicsConfiguration gc = device.getDefaultConfiguration();
		return gc.createCompatibleImage(w, h, transparency);
	}
	
	private void findCompatiblesDisplayMode() {
		for (DisplayMode displayMode: device.getDisplayModes()) {
			CompatiblesDisplay compatiblesDisplay = null;
			int width = displayMode.getWidth();
			int height = displayMode.getHeight();
			String key = width + "x" + height;
			if (compatiblesDisplayMap.containsKey(key)) {
				compatiblesDisplay = compatiblesDisplayMap.get(key);
				compatiblesDisplay.addDisplayMode(displayMode);
			} else {
				compatiblesDisplay = new CompatiblesDisplay(displayMode);
				compatiblesDisplayMap.put(key, compatiblesDisplay);
				compatiblesDisplayList.add(compatiblesDisplay);
			}	
		}
	}
	
	public Iterator<CompatiblesDisplay> iterator() {
		return compatiblesDisplayList.iterator();
	}

	public CompatiblesDisplay getCompatiblesDisplay(int width, int height) {
		return compatiblesDisplayMap.get(width + "x" + height);
	}

	public DisplayMode getCompatibleDisplayMode(int width, int height, int bitDepth, int refreshRate) {
		CompatiblesDisplay compatiblesDisplay = getCompatiblesDisplay(width, height);
		DisplayMode displayMode = null;
		if (compatiblesDisplay != null) {
			displayMode = compatiblesDisplay.getDisplayMode(bitDepth, refreshRate);
		}
		return displayMode;
	}
	
}
